package client;

import java.net.*;
import java.util.Date;
import java.io.*;

import client.messageContent.BalanceEnquiryMsgContent;
import client.messageContent.CloseAccountMsgContent;
import client.messageContent.DepositMsgContent;
import client.messageContent.TransferFundMsgContent;
import client.messageContent.WithdrawMsgContent;
import client.messageContent.CreateAccountMsgContent;
import client.messageContent.MonitorMsgContent;
import common.Constants;
import common.CurrencyEnum;
import common.data.Message;
import common.data.RequestTypeEnum;

public class UDPClient {
	DatagramSocket aSocket;
	RequestCreator requestCreator;

	public UDPClient(String serverIP) {

		this.requestCreator = RequestCreator.getCurrent(serverIP);
		try {
			this.aSocket = new DatagramSocket();
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void main(String args[]) {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("Enter server IP");
		String serverIP = null;
		try {
			serverIP = br.readLine();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		UDPClient udpClient = new UDPClient(serverIP);
		while (true) {
			try {
				// TODO: create menu for user
				System.out.println("Welcome to the bank.");
				System.out.println("1. Create account");
				System.out.println("2. Close account");
				System.out.println("3. Deposit money");
				System.out.println("4. Withdraw money");
				System.out.println("5. Monitor server");
				System.out.println("6. Balance Enquiry");
				System.out.println("7. Transfer Service");
				System.out.println("8. Exit");

				int choice = 0;
				try {
					choice = Integer.parseInt(br.readLine());
				} catch (IOException ioe) {
					System.out.println("Cannot read the choice");
					System.exit(1);
				}

				if (!udpClient.selectChoices(br, choice)) {
					break;
				}

				// Next option is for Monitor Server
				if (choice == 5) {
					//Client keeps waiting for msg from server until it receives msg saying that Monitor session is over.
					while (true) {
						byte[] buffer = new byte[Constants.BUFFER_LENGTH];
						DatagramPacket reply = new DatagramPacket(buffer,
								buffer.length);

						// System.out.println("Time out now is: " +
						// udpClient.aSocket.getSoTimeout());
						String messageReply = udpClient.receiveReply(reply);
						if (messageReply
								.equalsIgnoreCase("Monitoring session is over.")) { //Monitor session is over, display the menu
							System.out.println("End Monitoring Session at: "		//again for other options
									+ new Date());
							break;
						}

					}
				}
			} catch (Exception e) {
				e.printStackTrace();

			}
		}

	}

	private boolean selectChoices(BufferedReader br, int choice)
			throws IOException {
		DatagramPacket request = null;
		boolean result = true;
		switch (choice) {
		case 1: // create new account
			System.out.println("Going to create new bank account");
			request = this.createAccount(br);
			break;
		case 2: // close an account
			System.out.println("Going to close existing bank account");
			request = this.closeAccount(br);
			break;
		case 3: // deposit money into account
			System.out.println("Going to deposit into bank account");
			request = this.depositAccount(br);
			break;
		case 4: // withdraw money from account
			System.out.println("Going to withdraw from bank account");
			request = this.withdrawAccount(br);
			break;
		case 5: // monitor server
			System.out.println("Going to monitor server");
			request = this.monitorServer(br);
			break;
		case 6: // balance enquiry
			System.out.println("Going to enquire account balance");
			request = this.balEnquiryAccount(br);
			break;
		case 7: // transfer service
			System.out.println("Going to execute transfer service");
			request = this.transferFund(br);
			break;
		case 8:
			if (this.aSocket != null) {
				this.aSocket.close();
				result = false;
			}
			break;
		}
		if (!result)
			return false;
		while (true) {
			double a = Math.random();
			if (a < 0.95) {
				this.aSocket.send(request);
			} else
				System.out.println("a = " + a
						+ ", Simulate lost request package");

			this.aSocket.setSoTimeout(5000);

			System.out.println("Sent msg");

			byte[] buffer = new byte[Constants.BUFFER_LENGTH]; // a buffer
																// for
																// receive
			DatagramPacket reply = new DatagramPacket(buffer, buffer.length);

			try {
				this.receiveReply(reply);
				break;
			} catch (SocketTimeoutException e) {
				System.out.println("Client Timeout");

			}
		}
		
		//this option is to set again the timeout for monitoring server
		//if not, timeout is still 5s, and because client keep listening to server (we don't know
		//when server will reply), it will cause timeout exception
		if (choice == 5) {
			MonitorMsgContent monitorMsgContent = (MonitorMsgContent) requestCreator
					.getMsgContent();
			long t = monitorMsgContent.getTime();
			// System.out.println("Monitor time is: " + t);
			this.aSocket.setSoTimeout((int) t * 2);

		}
		return result;
	}

	private String receiveReply(DatagramPacket reply) throws IOException {
		this.aSocket.receive(reply);

		Message replyMsg = new Message(reply.getData());
		System.out.println(replyMsg.getMsgContentString());
		return replyMsg.getMsgContentString();
	}

	private DatagramPacket createAccount(BufferedReader br) {
		DatagramPacket request = null;
		// TODO: perform input error check
		try {
			System.out.println("Enter card holder name: ");
			String holderName = br.readLine();
			String password;
			while (true) {
				System.out.println("Enter password with length ".concat(String
						.valueOf(Constants.PASSWORD_LENGTH)));
				password = br.readLine();
				if (password.length() == Constants.PASSWORD_LENGTH) {
					break;
				}
				System.out.println("Length must be ".concat(String
						.valueOf(Constants.PASSWORD_LENGTH)));
			}
			System.out.println("Enter currency: 1 for USD or 0 for SGD");
			int currencyValue = Integer.parseInt(br.readLine());
			CurrencyEnum currency = CurrencyEnum.values()[currencyValue];

			System.out.println("Enter initial account balance");
			float balance = Float.parseFloat(br.readLine());

			System.out.println("Finish collecting data, sending msg");
			CreateAccountMsgContent createAccountMsgContent = new CreateAccountMsgContent(
					holderName, password, currency, balance);
			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.CREATE_ACCOUNT, createAccountMsgContent);

		} catch (Exception e) {
			e.printStackTrace();
		}

		return request;
	}

	private DatagramPacket balEnquiryAccount(BufferedReader br) {
		DatagramPacket request = null;
		try {
			System.out.println("Enter card holder name: ");
			String holderName = br.readLine();
			System.out.println("Enter account ID: ");
			String accIdRead = br.readLine();
			long accId = Long.parseLong(accIdRead);
			System.out.println("Enter account password:");
			String password = br.readLine();
			BalanceEnquiryMsgContent balEnquiryMsgContent = new BalanceEnquiryMsgContent(
					holderName, accId, password);
			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.BALANCE_ENQUIRY, balEnquiryMsgContent);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return request;
	}

	private DatagramPacket transferFund(BufferedReader br) {
		DatagramPacket request = null;
		try {
			System.out.println("Enter card holder name: ");
			String holderName = br.readLine();
			System.out.println("Enter account ID: ");
			String accIdRead = br.readLine();
			long accId = Long.parseLong(accIdRead);
			System.out.println("Enter account password:");
			String password = br.readLine();
			System.out.println("Enter recipient name:");
			String recipient = br.readLine();
			System.out.println("Enter recipient account ID: ");
			String accReciId = br.readLine();
			long recipientAccId = Long.parseLong(accReciId);
			System.out.println("Enter amount to transfer");
			String amount = br.readLine();
			float amountTransfer = Float.parseFloat(amount);
			System.out.println("Enter currency (SGD) or (USD):");
			String currency = br.readLine();
			TransferFundMsgContent transferMsgContent;
			if (currency.equals("SGD") || currency.equals("sgd")) {
				transferMsgContent = new TransferFundMsgContent(accId,
						holderName, password, recipientAccId, recipient,
						amountTransfer, CurrencyEnum.SGD);
			} else {
				transferMsgContent = new TransferFundMsgContent(accId,
						holderName, password, recipientAccId, recipient,
						amountTransfer, CurrencyEnum.USD);
			}
			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.TRANSFER_SERVICE, transferMsgContent);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return request;
	}

	private DatagramPacket closeAccount(BufferedReader br) {
		DatagramPacket request = null;
		try {
			System.out.println("Enter card holder name: ");
			String holderName = br.readLine();
			System.out.println("Enter account ID: ");
			String accIdRead = br.readLine();
			long accId = Long.parseLong(accIdRead);
			System.out.println("Enter account password:");
			String password = br.readLine();
			CloseAccountMsgContent closeAccountMsgContent = new CloseAccountMsgContent(
					holderName, accId, password);
			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.CLOSE_ACCOUNT, closeAccountMsgContent);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return request;
	}

	private DatagramPacket depositAccount(BufferedReader br) {
		DatagramPacket request = null;
		try {
			System.out.println("Enter card holder name: ");
			String holderName = br.readLine();
			System.out.println("Enter account ID: ");
			String accIdRead = br.readLine();
			long accId = Long.parseLong(accIdRead);
			System.out.println("Enter account password:");
			String password = br.readLine();
			float amountDeposit;
			while (true) {
				System.out.println("Enter amount deposit:");
				String amountDepo = br.readLine();
				amountDeposit = Float.parseFloat(amountDepo);
				if (amountDeposit > 0) {
					break;
				}
				System.out.println("Amount deposit must be positive");
			}
			System.out.println("Enter currency (SGD) or (USD):");
			String currency = br.readLine();
			DepositMsgContent depositMsgContent;
			if (currency.equals("SGD") || currency.equals("sgd")) {
				depositMsgContent = new DepositMsgContent(holderName, accId,
						password, CurrencyEnum.SGD, amountDeposit);
			} else {
				depositMsgContent = new DepositMsgContent(holderName, accId,
						password, CurrencyEnum.USD, amountDeposit);
			}
			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.DEPOSIT_ACCOUNT, depositMsgContent);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return request;
	}

	private DatagramPacket withdrawAccount(BufferedReader br) {

		DatagramPacket request = null;

		try {
			System.out.println("Enter card holder name: ");
			String holderName = br.readLine();
			System.out.println("Enter account ID: ");
			String accIdRead = br.readLine();
			long accId = Long.parseLong(accIdRead);
			System.out.println("Enter account password:");
			String password = br.readLine();
			float amountWithdraw;
			while (true) {
				System.out.println("Enter amount withdraw:");
				String amountWdr = br.readLine();
				amountWithdraw = Float.parseFloat(amountWdr);
				if (amountWithdraw > 0) {
					break;
				}
				System.out.println("Amount withdraw must be positive");
			}

			System.out.println("Enter currency (SGD) or (USD):");
			String currency = br.readLine();
			WithdrawMsgContent withdrawMsgContent;
			if (currency.equals("SGD") || currency.equals("sgd")) {
				withdrawMsgContent = new WithdrawMsgContent(holderName, accId,
						password, CurrencyEnum.SGD, amountWithdraw);
			} else {
				withdrawMsgContent = new WithdrawMsgContent(holderName, accId,
						password, CurrencyEnum.USD, amountWithdraw);
			}

			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.WITHDRAW_ACCOUNT, withdrawMsgContent);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return request;
	}
	
	
	//Method for monitor server
	private DatagramPacket monitorServer(BufferedReader br) {
		
		DatagramPacket request = null;

		try {
			//We need to ask for time duration, and send it to server
			System.out
					.println("Enter duration (in second) that you want to moniter: ");
			long time = Long.parseLong(br.readLine());
//			System.out.println("Finish collecting data, sending msg");

			MonitorMsgContent monitorMsgContent = new MonitorMsgContent(
					time * 1000);
			this.aSocket.setSoTimeout((int) (time * 2000));
			request = this.requestCreator.CreateMessage(
					RequestTypeEnum.MONITOR_SERVER, monitorMsgContent);

		} catch (Exception e) {
			e.printStackTrace();
		}
		return request;
	}

}
